/*
	gamecard-microsd
	Copyright 2017-2018, xyz

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
	This software contains code derived from VitaShell.
	Included below is the license header from VitaShell.
*/

/*
	VitaShell
	Copyright (C) 2015-2017, TheFloW

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <psp2kern/kernel/cpu.h>
#include <psp2kern/kernel/modulemgr.h>
#include <psp2kern/kernel/sysmem.h>
#include <psp2kern/kernel/threadmgr.h>
#include <psp2kern/io/fcntl.h>
#include <psp2kern/io/dirent.h>
#include <psp2kern/kernel/iofilemgr.h>

#include <stdio.h>
#include <string.h>

#include <taihen.h>

#define UX0_DEV "ux0:"
#define UX0_DEV2 "exfatux0"
#define UX0_ID 0x800

#define IMC0_DEV "imc0:"
#define IMC0_DEV2 "exfatimc0"
#define IMC0_ID 0xD00

#define XMC0_DEV "xmc0:"
#define XMC0_DEV2 "exfatxmc0"
#define XMC0_ID 0xE00

#define UMA0_DEV "uma0:"
#define UMA0_DEV2 "exfatuma0"
#define UMA0_ID 0xF00

#define XMC_BLKDEV "sdstor0:xmc-lp-ign-userext"
#define XMC_BLKDEV2 "sdstor0:xmc-lp-ign-userext"

#define INT_BLKDEV "sdstor0:int-lp-ign-userext"
#define INT_BLKDEV2 "sdstor0:int-lp-ign-userext"

#define GCD_BLKDEV "sdstor0:gcd-lp-ign-entire"
#define GCD_BLKDEV2 "sdstor0:gcd-lp-ign-entire"

int module_get_offset(SceUID pid, SceUID modid, int segidx, size_t offset, uintptr_t *addr);

typedef struct
{
	const char *dev;
	const char *dev2;
	const char *blkdev;
	const char *blkdev2;
	int id;
} SceIoDevice;

typedef struct
{
	int id;
	const char *dev_unix;
	int unk;
	int dev_major;
	int dev_minor;
	const char *dev_filesystem;
	int unk2;
	SceIoDevice *dev;
	int unk3;
	SceIoDevice *dev2;
	int unk4;
	int unk5;
	int unk6;
	int unk7;
} SceIoMountPoint;

// 1 xmc-ux0, gcd-uma0, 2 gcd-ux0, xmc-uma0
#define MOUNT_MODE 2

// 1 gcd-uma0, xmc-ux0
static SceIoDevice gcd_uma0_dev = {UMA0_DEV, UMA0_DEV2, GCD_BLKDEV, GCD_BLKDEV2, UMA0_ID};
// static SceIoDevice xmc_ux0_dev = { UX0_DEV, UX0_DEV2, XMC_BLKDEV, XMC_BLKDEV2, UX0_ID };
//  2 gcd-ux0, xmc-uma0
static SceIoDevice gcd_ux0_dev = {UX0_DEV, UX0_DEV2, GCD_BLKDEV, GCD_BLKDEV2, UX0_ID};
static SceIoDevice xmc_uma0_dev = {UMA0_DEV, UMA0_DEV2, XMC_BLKDEV, XMC_BLKDEV2, UMA0_ID};
// int-imc0 (psv2000|tv)
static SceIoDevice int_imc0_dev = {IMC0_DEV, IMC0_DEV2, INT_BLKDEV, INT_BLKDEV2, IMC0_ID};

static SceIoDevice *mount_devs[3] = {NULL, NULL, NULL};
static SceIoDevice *ori_devs[3] = {NULL, NULL, NULL};
static SceIoDevice *ori_devs2[3] = {NULL, NULL, NULL};

static SceIoMountPoint *(*sceIoFindMountPoint)(int id) = NULL;

static SceUID hooks[2];

int checkFolderExist(const char *folder)
{
	SceUID dfd = ksceIoDopen(folder);
	if (dfd < 0)
		return 0;

	ksceIoDclose(dfd);
	return 1;
}

static void io_unmount(int id)
{
	ksceIoUmount(id, 0, 0, 0);
	ksceIoUmount(id, 1, 0, 0);
}

static void io_mount(int id)
{
	ksceIoMount(id, NULL, 0, 0, 0, 0);
}

static void io_remount(int id)
{
	io_unmount(id);
	io_mount(id);
}

// mount
int shellKernelIsRedirected(SceIoDevice *mountDev)
{
	SceIoMountPoint *mount = sceIoFindMountPoint(mountDev->id);
	if (!mount)
	{
		return -1;
	}

	if (mount->dev == mountDev && mount->dev2 == mountDev)
	{
		return 1;
	}

	return 0;
}

int shellKernelRedirect(SceIoDevice *mountDev, SceIoDevice *oriDev, SceIoDevice *oriDev2)
{
	SceIoMountPoint *mount = sceIoFindMountPoint(mountDev->id);
	if (!mount)
	{
		return -1;
	}

	if (mount->dev != mountDev && mount->dev2 != mountDev)
	{
		oriDev = mount->dev;
		oriDev2 = mount->dev2;
	}

	mount->dev = mountDev;
	mount->dev2 = mountDev;

	io_remount(mountDev->id);

	return 0;
}

int shellKernelUnredirect(int id, SceIoDevice *oriDev, SceIoDevice *oriDev2)
{
	SceIoMountPoint *mount = sceIoFindMountPoint(id);
	if (!mount)
	{
		return -1;
	}

	if (oriDev && oriDev2)
	{
		mount->dev = oriDev;
		mount->dev2 = oriDev2;

		oriDev = NULL;
		oriDev2 = NULL;
	}

	io_remount(id);

	return 0;
}

// redirect by theflow
int redirect(int mode)
{
	// Get tai module info
	tai_module_info_t info;
	info.size = sizeof(tai_module_info_t);
	if (taiGetModuleInfoForKernel(KERNEL_PID, "SceIofilemgr", &info) < 0)
		return -1;

	// Get important function
	switch (info.module_nid)
	{
	case 0x9642948C: // 3.60 retail
		module_get_offset(KERNEL_PID, info.modid, 0, 0x138C1, (uintptr_t *)&sceIoFindMountPoint);
		break;

	case 0xA96ACE9D: // 3.65 retail
	case 0x3347A95F: // 3.67 retail
	case 0x90DA33DE: // 3.68 retail
		module_get_offset(KERNEL_PID, info.modid, 0, 0x182F5, (uintptr_t *)&sceIoFindMountPoint);
		break;

	case 0xF16E72C7: // 3.69 retail
	case 0x81A49C2B: // 3.70 retail
	case 0xF2D59083: // 3.71 retail
	case 0x9C16D40A: // 3.72 retail
	case 0xF7794A6C: // 3.73 retail
	case 0x796DAFAF: // 3.74 retail
		module_get_offset(KERNEL_PID, info.modid, 0, 0x18735, (uintptr_t *)&sceIoFindMountPoint);
		break;

	default:
		return -1;
	}

	switch (mode)
	{
	case 1:
		mount_devs[0] = &gcd_uma0_dev; // gcd:
		mount_devs[1] = NULL;		   // xmc: // mount_devs[1] = &xmc_ux0_dev; no need to do this
		mount_devs[2] = &int_imc0_dev; // int:
		break;

	case 2:
		mount_devs[0] = &gcd_ux0_dev;  // gcd:
		mount_devs[1] = &xmc_uma0_dev; // xmc:
		mount_devs[2] = &int_imc0_dev; // int:
		break;
	}

	for (int i = 0; i < 3; i++)
	{
		if (mount_devs[i] == NULL)
		{ // NULL
			continue;
		}
		if ((strcmp(mount_devs[i]->blkdev, XMC_BLKDEV) == 0 || strcmp(mount_devs[i]->blkdev, INT_BLKDEV) == 0) && !checkFolderExist(UX0_DEV))
		{
			mount_devs[i]->dev = UX0_DEV;
			mount_devs[i]->dev2 = UX0_DEV2;
			mount_devs[i]->id = UX0_ID;
		}
		// redirect
		shellKernelRedirect(mount_devs[i], ori_devs[i], ori_devs2[i]);
	}

	return 0;
}

int poke_gamecard()
{
	tai_module_info_t info;
	info.size = sizeof(tai_module_info_t);
	if (taiGetModuleInfoForKernel(KERNEL_PID, "SceSdstor", &info) < 0)
		return -1;

	void *args = 0;
	int (*int_insert)() = 0;
	int (*int_remove)() = 0;

	module_get_offset(KERNEL_PID, info.modid, 0, 0x3BD5, (uintptr_t *)&int_insert);
	module_get_offset(KERNEL_PID, info.modid, 0, 0x3BC9, (uintptr_t *)&int_remove);

	module_get_offset(KERNEL_PID, info.modid, 1, 0x1B20 + 40 * 1, (uintptr_t *)&args);

	int_remove(0, args);
	ksceKernelDelayThread(500 * 1000);
	int_insert(0, args);
	ksceKernelDelayThread(500 * 1000);

	return 0;
}

tai_hook_ref_t hook_get_partition;
tai_hook_ref_t hook_write;
tai_hook_ref_t hook_mediaid;

uint32_t magic = 0x7FFFFFFF;

void *sdstor_mediaid;

void *my_get_partition(const char *name, size_t len)
{
	void *ret = TAI_CONTINUE(void *, hook_get_partition, name, len);
	if (!ret && len == 18 && strcmp(name, "gcd-lp-act-mediaid") == 0)
	{
		return &magic;
	}
	return ret;
}

uint32_t my_write(uint8_t *dev, void *buf, uint32_t sector, uint32_t size)
{
	if (dev[36] == 1 && sector == magic)
	{
		return 0;
	}
	return TAI_CONTINUE(uint32_t, hook_write, dev, buf, sector, size);
}

uint32_t my_mediaid(uint8_t *dev)
{
	uint32_t ret = TAI_CONTINUE(uint32_t, hook_mediaid, dev);

	if (dev[36] == 1)
	{
		memset(dev + 20, 0xFF, 16);
		memset(sdstor_mediaid, 0xFF, 16);

		return magic;
	}
	return ret;
}

// allow SD cards, patch by motoharu
void patch_sdstor()
{
	tai_module_info_t sdstor_info;
	sdstor_info.size = sizeof(tai_module_info_t);
	if (taiGetModuleInfoForKernel(KERNEL_PID, "SceSdstor", &sdstor_info) < 0)
		return;

	module_get_offset(KERNEL_PID, sdstor_info.modid, 1, 0x1720, (uintptr_t *)&sdstor_mediaid);

	// patch for proc_initialize_generic_2 - so that sd card type is not ignored
	char zeroCallOnePatch[4] = {0x01, 0x20, 0x00, 0xBF};
	taiInjectDataForKernel(KERNEL_PID, sdstor_info.modid, 0, 0x2498, zeroCallOnePatch, 4); // patch (BLX) to (MOVS R0, #1 ; NOP)
	taiInjectDataForKernel(KERNEL_PID, sdstor_info.modid, 0, 0x2940, zeroCallOnePatch, 4);

	taiHookFunctionOffsetForKernel(KERNEL_PID, &hook_get_partition, sdstor_info.modid, 0, 0x142C, 1, my_get_partition);
	taiHookFunctionOffsetForKernel(KERNEL_PID, &hook_write, sdstor_info.modid, 0, 0x2C58, 1, my_write);
	taiHookFunctionOffsetForKernel(KERNEL_PID, &hook_mediaid, sdstor_info.modid, 0, 0x3D54, 1, my_mediaid);
}

// allow Memory Card remount
void patch_appmgr()
{
	tai_module_info_t appmgr_info;
	appmgr_info.size = sizeof(tai_module_info_t);
	if (taiGetModuleInfoForKernel(KERNEL_PID, "SceAppMgr", &appmgr_info) >= 0)
	{
		uint32_t nop_nop_opcode = 0xBF00BF00;
		switch (appmgr_info.module_nid)
		{
		case 0xDBB29DB7: // 3.60 retail
		case 0x1C9879D6: // 3.65 retail
			hooks[0] = taiInjectDataForKernel(KERNEL_PID, appmgr_info.modid, 0, 0xB338, &nop_nop_opcode, 4);
			hooks[1] = taiInjectDataForKernel(KERNEL_PID, appmgr_info.modid, 0, 0xB368, &nop_nop_opcode, 2);
			break;

		case 0x54E2E984: // 3.67 retail
		case 0xC3C538DE: // 3.68 retail
			hooks[0] = taiInjectDataForKernel(KERNEL_PID, appmgr_info.modid, 0, 0xB344, &nop_nop_opcode, 4);
			hooks[1] = taiInjectDataForKernel(KERNEL_PID, appmgr_info.modid, 0, 0xB374, &nop_nop_opcode, 2);
			break;

		case 0x321E4852: // 3.69 retail
		case 0x700DA0CD: // 3.70 retail
		case 0xF7846B4E: // 3.71 retail
		case 0xA8E80BA8: // 3.72 retail
		case 0xB299D195: // 3.73 retail
		case 0x30007BD3: // 3.74 retail
			hooks[0] = taiInjectDataForKernel(KERNEL_PID, appmgr_info.modid, 0, 0xB34C, &nop_nop_opcode, 4);
			hooks[1] = taiInjectDataForKernel(KERNEL_PID, appmgr_info.modid, 0, 0xB37C, &nop_nop_opcode, 2);
			break;
		}
	}
}

void _start() __attribute__((weak, alias("module_start")));
int module_start(SceSize args, void *argp)
{
	patch_sdstor();
	patch_appmgr();
	poke_gamecard();
	redirect(MOUNT_MODE);

	return SCE_KERNEL_START_SUCCESS;
}

int module_stop(SceSize args, void *argp)
{
	if (hooks[1] >= 0)
		taiInjectReleaseForKernel(hooks[1]);

	if (hooks[0] >= 0)
		taiInjectReleaseForKernel(hooks[0]);

	return SCE_KERNEL_STOP_SUCCESS;
}
